package org.scboy.coobi.scan;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Date;
import java.util.Enumeration;
import java.util.List;
import java.util.Locale;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.parser.Parser;
import org.jsoup.parser.XmlTreeBuilder;
import org.jsoup.select.Elements;
import org.scboy.coobi.caches.DbTableCache;
import org.scboy.coobi.caches.InitDataCache;
import org.scboy.coobi.caches.SqlCache;
import org.scboy.coobi.utils.FileUtil;
import org.scboy.coobi.utils.JarPathUtil;
import org.scboy.utils.entitys.coobi.DbColumn;
import org.scboy.utils.entitys.coobi.DbIndex;
import org.scboy.utils.entitys.coobi.DbTable;
import org.scboy.utils.entitys.coobi.Model;
import org.scboy.utils.entitys.coobi.SQLConfig;
import org.scboy.utils.entitys.coobi.SQLEntity;

import com.fasterxml.jackson.databind.ObjectMapper;

import cn.hutool.log.Log;
import cn.hutool.log.LogFactory;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;

/**
 * @author 四川耗子
 * @email 125167488@qq.com
 * @date 2017年7月4日
 */
public class FileScan {

	static Log log=LogFactory.get();

	private static List<InputStream> sql_ls=new ArrayList<InputStream>();
	
	private static List<String> chaset_ls=new ArrayList<String>();
	
	
	public static void setDbEntity(DbTable entity){
		if(DbTableCache.getDbTable(entity.getName())!=null){
			//当前内存数据表版本小于新加入的对象实体版本，更新内存变量。
			if(DbTableCache.getDbTable(entity.getName()).getVersion() < entity.getVersion()){
				DbTableCache.setDbTable(entity.getName(), entity);
			}
		}else{
			DbTableCache.setDbTable(entity.getName(), entity);
		}
	}
	
	/**
	 * 读取sql配置文件
	 * @param stream
	 * @param encoding
	 * @param sqlType
	 * @param alias
	 */
	private static void readSqlConfig(InputStream stream,String encoding){
		String sqlString=FileUtil.readText(stream, encoding);
		//把备注替换掉。
		sqlString=sqlString.replaceAll("--\\*[^(\\*--)]*\\*--", "");
		Matcher matcher = Pattern.compile("\\s*--id:(?<id>[0-9A-Za-z_]*)\\s*--name:(?<name>[0-9A-Za-z_\u4e00-\u9fa5]*)\\s*--version:(?<version>[0-9]+)").matcher(sqlString);
		int indexStart=0;
		String id="";
		String version="";
		String name="";
		String sql="";
		while (matcher.find()) {
			if(indexStart!=0){
				sql=sqlString.substring(indexStart,matcher.start());
				SQLConfig sqlConfig=SqlCache.getConfig(id);
				if(sqlConfig==null){
					sqlConfig=new SQLConfig();
					sqlConfig.setId(id);
					sqlConfig.setName(name);
					SqlCache.setConfig(sqlConfig);
				}
				SQLEntity entity=new SQLEntity();
				entity.setName(name);
				entity.setNote(entity.getNote());
				entity.setSql(sql);
				entity.setVersion(Integer.parseInt(version));
				sqlConfig.setSql(entity);
			}
			id=matcher.group("id");
			name=matcher.group("name");
			version=matcher.group("version");
			indexStart=matcher.end();
		}
		if(indexStart!=0){
			sql=sqlString.substring(indexStart);
			SQLConfig sqlConfig=SqlCache.getConfig(id);
			if(sqlConfig==null){
				sqlConfig=new SQLConfig();
				sqlConfig.setId(id);
				sqlConfig.setName(name);
				SqlCache.setConfig(sqlConfig);
			}
			SQLEntity entity=new SQLEntity();
			entity.setName(sqlConfig.getName());
			entity.setNote(entity.getNote());
			entity.setSql(sql);
			entity.setVersion(Integer.parseInt(version));
			sqlConfig.setSql(entity);
		}
	}
	
	/**
	 * 读取配置文件，加入到内存中。
	 * @param file
	 * @param alias
	 * @throws FileNotFoundException
	 * @throws IOException
	 */
	public static void jarConfig(JarFile jar,JarEntry entry) throws FileNotFoundException, IOException{
		String fname=entry.getName();
		if(fname.lastIndexOf("/")>-1){
			fname=fname.substring(fname.lastIndexOf("/")+1);
		}
		if (fname.toLowerCase(Locale.ENGLISH).endsWith(".entity.json")||fname.toLowerCase(Locale.ENGLISH).endsWith(".entity.xml")) {
			//
			//初始化表配置
			//读取sql配置文件，文件以.entity.json和.entity.xml
			//读取的数据放入内存中，在放入内存时，检查当前内存中配置版本是否低于最新读取的版本
			//如果内存版本低于最新读取到的配置版本，把内存表配置替换成最新读取的配置。
			//
			if(fname.toLowerCase(Locale.ENGLISH).endsWith(".entity.json")){
				//json方式
				String name=fname.replaceAll("\\.entity\\.json", "");
				log.info("扫描到表[JSON]["+name+"]配置,路径：["+jar.getName()+"!"+entry.getName()+"]");
				String charset=FileUtil.get_charset(jar.getInputStream(entry));
				setDbEntity(readTable(name,jar.getInputStream(entry), "json", charset));
			}else if(fname.toLowerCase(Locale.ENGLISH).endsWith(".entity.xml")){
				//xml方式
				String entityName=fname.replaceAll("\\.entity\\.xml", "");
				log.info("扫描到表[XML]["+entityName+"]配置,路径：["+jar.getName()+"!"+entry.getName()+"]");
				String charset=FileUtil.get_charset(jar.getInputStream(entry));
				setDbEntity(readTable(entityName, jar.getInputStream(entry), "xml", charset));
			}
		} else if(fname.toLowerCase(Locale.ENGLISH).endsWith(".data.json")) {
			
			//
			//初始化数据
			//读取后缀为.data.json格式的文件，已文件名称对应表，初始化表数据。
			//
			String name=fname.replaceAll("\\.data\\.json", "");
			log.info("扫描到表[JSON]["+name+"]初始化数据配置,路径：["+jar.getName()+"!"+entry.getName()+"]");
			String charset=FileUtil.get_charset(jar.getInputStream(entry));
			if(!InitDataCache.initData.containsKey(name.toUpperCase(Locale.ENGLISH))){
				InitDataCache.initData.put(name.toUpperCase(Locale.ENGLISH), new ArrayList<Model>());
			}
			InitDataCache.initData.get(name.toUpperCase(Locale.ENGLISH)).add(Model.create().add("charset", charset).add("inputStream", jar.getInputStream(entry)));
		} else{
			if(fname.toLowerCase(Locale.ENGLISH).endsWith(".sql")){
				String name=fname.replaceAll(".sql", "");
				log.info("SQL配置文件["+name+"],路径：["+jar.getName()+"!"+entry.getName()+"]");
				//获取编码
				String charset=FileUtil.get_charset(jar.getInputStream(entry));
				sql_ls.add(jar.getInputStream(entry));
				chaset_ls.add(charset);
			}else if(fname.toLowerCase(Locale.ENGLISH).endsWith(".sql.xml")) {
				log.info("SQL[XML]配置文件,路径：["+jar.getName()+"!"+entry.getName()+"]");
				readXmlSql(jar.getInputStream(entry));
			}
		}
	}
	
	
	/**
	 * 读取配置文件，加入到内存中。
	 * @param file
	 * @param alias
	 * @throws FileNotFoundException
	 * @throws IOException
	 */
	public static void fileConfig(File file) throws FileNotFoundException, IOException{
		String fname=file.getName();
		if (fname.toLowerCase(Locale.ENGLISH).endsWith(".entity.json")||fname.toLowerCase(Locale.ENGLISH).endsWith(".entity.xml")) {
			//
			//初始化表配置
			//读取sql配置文件，文件以.entity.json和.entity.xml
			//读取的数据放入内存中，在放入内存时，检查当前内存中配置版本是否低于最新读取的版本
			//如果内存版本低于最新读取到的配置版本，把内存表配置替换成最新读取的配置。
			//
			if(fname.toLowerCase(Locale.ENGLISH).endsWith(".entity.json")){
				//json方式
				String name=fname.replaceAll("\\.entity\\.json", "");
				log.info("扫描到表[JSON]["+name+"]配置,路径：["+file.getAbsolutePath()+"]");
				String charset=FileUtil.get_charset(new FileInputStream(file));
				setDbEntity(readTable(name, new FileInputStream(file), "json", charset));
			}else if(fname.toLowerCase(Locale.ENGLISH).endsWith(".entity.xml")){
				//xml方式
				String entityName=fname.replaceAll("\\.entity\\.xml", "");
				log.info("扫描到表[XML]["+entityName+"]配置,路径：["+file.getAbsolutePath()+"]");
				String charset=FileUtil.get_charset(new FileInputStream(file));
				setDbEntity(readTable(entityName, new FileInputStream(file), "xml", charset));
			}
		} else if(fname.toLowerCase(Locale.ENGLISH).endsWith(".data.json")) {
			//
			//初始化数据
			//读取后缀为.data.json格式的文件，已文件名称对应表，初始化表数据。
			//
			String name=fname.replaceAll("\\.data\\.json", "");
			log.info("扫描到表[JSON]["+name+"]初始化数据配置,路径：["+file.getAbsolutePath()+"]");
			String charset=FileUtil.get_charset(new FileInputStream(file));
			//List<Map<String, Object>> dataList=JsonUtils.toEntity(readText(new FileInputStream(file), charset),(new  ArrayList<Map<String, Object>>()).getClass());
			if(!InitDataCache.initData.containsKey(name.toUpperCase(Locale.ENGLISH))){
				InitDataCache.initData.put(name.toUpperCase(Locale.ENGLISH), new ArrayList<Model>());
			}
			InitDataCache.initData.get(name.toUpperCase(Locale.ENGLISH)).add(Model.create().add("charset", charset).add("inputStream", new FileInputStream(file)));
		} else{
			if(fname.toLowerCase(Locale.ENGLISH).endsWith(".sql")){
				String name=fname.replaceAll(".sql", "");
				log.info("SQL配置文件["+name+"],路径：["+file.getAbsolutePath()+"]");
				//获取编码
				String charset=FileUtil.get_charset(new FileInputStream(file));
				sql_ls.add(new FileInputStream(file));
				chaset_ls.add(charset);
			}else if(fname.toLowerCase(Locale.ENGLISH).endsWith(".sql.xml")) {
				//xml方式
				log.info("SQL配置文件[XML]配置,路径：["+file.getAbsolutePath()+"]");
				readXmlSql(new FileInputStream(file));
			}
		}
	}
	
	
	
	/**
	 * 文件夹
	 * @param dir
	 * @throws IOException 
	 * @throws FileNotFoundException 
	 */
	public static void scanDir(File dir) throws FileNotFoundException, IOException{
		File [] files=dir.listFiles();
		for (File file : files) {
			if(file.isFile()){
				fileConfig(file);
			}else{
				scanDir(file);
			}
		}
	}
	
	/**
	 * 扫描配置信息
	 */
	public static void scan(){
		// 定义一个枚举的集合 并进行循环来处理这个目录下的things
		Enumeration<URL> dirs;
		log.info("========================开始扫描所有包的配置==============================");
		try {
			//从classpath根开始扫描
			dirs = Thread.currentThread().getContextClassLoader().getResources("");
			// 循环迭代下去
			while (dirs.hasMoreElements()) {
				// 获取下一个元素
				URL url = dirs.nextElement();
				// 得到协议的名称
				String protocol = url.getProtocol();
				if ("file".equals(protocol)) {
					// 获取包的物理路径
					String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
					File file = new File(filePath);
					if(file.isDirectory()){
						//如果是文件夹
						scanDir(file);
					}else{
						//如果是文件
						fileConfig(file);
					}
				} else if ("jar".equals(protocol)) {
					JarFile jar = ((JarURLConnection) url.openConnection())
							.getJarFile();
					// 从此jar包 得到一个枚举类
					Enumeration<JarEntry> entries = jar.entries();
					while (entries.hasMoreElements()) {
						JarEntry entry = entries.nextElement();
						//如果是文件夹，继续下一个。
						if(entry.isDirectory()){
							continue;
						}
						jarConfig(jar, entry);
					}
				}
			}
			//处理sql
			for (int i = 0; i < sql_ls.size(); i++) {
				readSqlConfig(sql_ls.get(i), chaset_ls.get(i));
			}
		} catch (IOException e) {
			throw new RuntimeException("初始化异常，扫描配置文件出错。",e);
		}
		
		//重置sql内存
		sql_ls=new ArrayList<InputStream>();
		chaset_ls=new ArrayList<String>();
		log.info("-------------------------开始扫描缓存配置文件-------------------------");
		String timeDic=DateUtil.format(new Date(), "yyyyMMdd");
		String cachePath=JarPathUtil.getLocation()+File.separator+"resource"+File.separator+timeDic;
		log.info("缓存资源路径："+cachePath);
		//加载缓存目录的配置文件
		try {
			//
			File file = new File(cachePath);
			//判断资源文件是否存在
			if(file.exists()) {
				if(file.isDirectory()){
					//如果是文件夹
					scanDir(file);
				}else{
					//如果是文件
					fileConfig(file);
				}
				//处理sql
				for (int i = 0; i < sql_ls.size(); i++) {
					readSqlConfig(sql_ls.get(i), chaset_ls.get(i));
				}
			}
		} catch (IOException e) {
			throw new RuntimeException("初始化异常，扫描配置文件出错。",e);
		}
	}
	
	/**
	 * 读取table的配置文件
	 * @return
	 */
	public static DbTable readTable(String tablename,InputStream inputStream,String type,String charset){
		try {
			DbTable dbEntity = null;
			if(type.equals("json")){
				//json方式的配置文件
				String configJson =FileUtil.readText(inputStream,charset);
				ObjectMapper mapper=new ObjectMapper();
				dbEntity = mapper.readValue(configJson, DbTable.class);
			} else if(type.equals("xml")){
				//XML方式的配置文件
				Document doc=Jsoup.parse(inputStream,"UTF-8","",new Parser(new XmlTreeBuilder()));
				dbEntity=new DbTable();
				dbEntity.setName(doc.getElementsByTag("name").text().replace("\\s", ""));
				dbEntity.setVersion(Integer.parseInt(doc.getElementsByTag("version").text().replace("\\s", "")));
				dbEntity.setComment(doc.getElementsByTag("comment").text().replace("\\s", ""));
				
				//读取列配置
				List<DbColumn> columns=new ArrayList<DbColumn>();
				for (Element el : doc.select("columns column")) {
					DbColumn column=new DbColumn();
					column.setComment(el.attr("comment").replace("\\s", ""));
					column.setDbType(el.attr("dbType").replace("\\s", ""));
					column.setEntityName(el.attr("entityName").replace("\\s", ""));
					Integer length=Convert.toInt(el.attr("length").replace("\\s", ""));
					if(length!=null) {
						column.setLength(length);
					}
					column.setName(el.attr("name").replace("\\s", ""));
					column.setNotNull(el.attr("notNull").replace("\\s", "").equals("true")?true:false);
					column.setPrimary(el.attr("primary").equals("true"));
					columns.add(column);
				}
				dbEntity.setColumns(columns);
				
				//读取索引配置
				List<DbIndex> indexs=new ArrayList<DbIndex>();
				for (Element el : doc.select("indexs index")) {
					DbIndex index=new DbIndex();
					index.setIndexName(el.attr("id").replace("\\s", ""));
					index.setColumns(el.text().trim());
					indexs.add(index);
				}
				dbEntity.setIndexs(indexs);
			}
			
			if(dbEntity==null) {
				throw new RuntimeException("无法读取配置表数据。");
			}
			for (DbColumn column : dbEntity.getColumns()) {
				column.setDbType(column.getDbType().toUpperCase(Locale.ENGLISH));
			}
			//如果没有配置表名称，以文件名称做为表名称
			if(StrUtil.isEmpty(dbEntity.getName())){
				dbEntity.setName(tablename.toUpperCase(Locale.ENGLISH));
			}else{
				dbEntity.setName(dbEntity.getName().toUpperCase(Locale.ENGLISH));
			}
			return dbEntity;
		} catch (Exception e) {
			log.error("读取表的配置文件异常["+tablename+"],异常：{}",e.getMessage());
			throw new RuntimeException(e);
		}finally {
			IoUtil.close(inputStream);
		}
	}
	
	public static void readXmlSql(InputStream inputStream){
		try {
			//XML方式的配置文件
			Document doc=Jsoup.parse(inputStream,"UTF-8","",new Parser(new XmlTreeBuilder()));
			doc.outputSettings().prettyPrint(false);
			
			Elements elements= doc.select("sql");
			for (Element element : elements) {
				String id=element.attr("id");
				String name=element.attr("name");
				int version=Convert.toInt(element.attr("version"));
				String sql=element.html();
				SQLConfig sqlConfig=SqlCache.getConfig(id);
				if(sqlConfig==null){
					sqlConfig=new SQLConfig();
					sqlConfig.setId(id);
					sqlConfig.setName(name);
					SqlCache.setConfig(sqlConfig);
				}
				SQLEntity entity=new SQLEntity();
				entity.setName(sqlConfig.getName());
				entity.setSql(sql);
				entity.setVersion(version);
				sqlConfig.setSql(entity);
			}
		} catch (Exception e) {
			log.error("读取表的配置文件异常,异常：{}",e.getMessage());
			throw new RuntimeException(e);
		}finally {
			IoUtil.close(inputStream);
		}
	}
	
	/**
	 * 读取文本
	 * 
	 * @param stream 数据流
	 * @return
	 */
	public static String readText(InputStream inputStream,String encoding) {
		StringBuilder txt=new StringBuilder("");
		try {
			
			InputStreamReader read = new InputStreamReader(inputStream,encoding);//考虑到编码格式
        	BufferedReader bufferedReader = new BufferedReader(read);
        	String lineTxt = null;
        	while((lineTxt = bufferedReader.readLine()) != null){
        		txt.append(lineTxt);
        		txt.append(System.getProperty("line.separator"));
        	}
		} catch (Exception e) {
			log.error(e);
		} finally {
			IoUtil.close(inputStream);
		}
		return txt.toString();
	}

}
